/*
 * File:   main.cpp
 * Author: Tung Mai
 *
 * Created on April 8, 2010, 2:01 PM
 */

#include <CoreAudio/CoreAudio.h>
#include <IOKit/audio/IOAudioTypes.h>

bool IsMicDevice(AudioDeviceID inDeviceID)
{
    bool result = false;
    UInt32 size;
    UInt32 dataSource;
    OSStatus err;

    size = sizeof(UInt32);
    AudioObjectPropertyAddress propAddress = { kAudioDevicePropertyDataSource,
        kAudioDevicePropertyScopeInput, kAudioObjectPropertyElementMaster };
    err = AudioObjectGetPropertyData(inDeviceID, &propAddress, 0, NULL, &size, &dataSource);
    if (err)
    {
        // Perhaps get data source will succeed on built-in device and fail on
        // external one, need confirmation here
        printf("Get data source failed with error %ld\n", err);
    }

    if (dataSource == kIOAudioInputPortSubTypeInternalMicrophone ||
        dataSource == kIOAudioInputPortSubTypeExternalMicrophone)
    {
        return true;
    }

    // Continue to check terminal type
    propAddress.mSelector = kAudioDevicePropertyStreams;
    err = AudioObjectGetPropertyDataSize(inDeviceID, &propAddress, 0, NULL, &size);
    if (err)
    {
        printf("Get streams size of inDeviceID failed with error %ld\n", err);
        return false;
    }

    int numStreams = size / sizeof(AudioStreamID);
    printf("Number of streams: %d\n", numStreams);

    if (numStreams > 0)
    {
        AudioStreamID *streams = (AudioStreamID *) malloc(size);
        memset(streams, 0, size);

        err = AudioObjectGetPropertyData(inDeviceID, &propAddress, 0, NULL, &size, streams);
        if (err)
        {
            printf("Get streams property of inDeviceID failed.\n");
            free(streams);
            return false;
        }

        UInt32 terminalType;
        size = sizeof(UInt32);
        propAddress.mSelector = kAudioStreamPropertyTerminalType;
        err = AudioObjectGetPropertyData(streams[0], &propAddress, 0, NULL, &size, &terminalType);
        if (err)
        {
            printf("Get stream terminal type failed.\n");
            free(streams);
            return false;
        }
        printf("terminalType: %x\n", terminalType);
        if ( (INPUT_UNDEFINED <= terminalType && terminalType <= INPUT_MODEM_AUDIO) ||
            terminalType == INPUT_NULL)
        {
            result = true;
        }

        free(streams);
    }

    return result;
}

OSStatus MyAudioObjectPropertyListenerProc(AudioObjectID inObjectID, UInt32 inNumberAddresses,
        const AudioObjectPropertyAddress inAddresses[], void* inClientData)
{
    printf("Default input device has been changed\n");
    printf("inObjectID: %d\n", inObjectID);
    printf("number of addresses passed in: %d\n", inNumberAddresses);

    for (int i=0; i<inNumberAddresses; i++)
    {
        AudioObjectPropertyAddress propAddress = inAddresses[i];
        if (inObjectID == kAudioObjectSystemObject &&
            propAddress.mSelector == kAudioHardwarePropertyDefaultInputDevice)
        {
            AudioDeviceID newDefaultInputDeviceID;
            UInt32 size = sizeof(AudioDeviceID);
            OSStatus err = AudioObjectGetPropertyData(inObjectID, &propAddress,
                    0, NULL, &size, &newDefaultInputDeviceID);
            if (err)
            {
                printf("Get new default input device failed with error %d\n", err);
                return err;
            }
            printf("newDefaultInputDeviceID: %d\n", newDefaultInputDeviceID);
            AudioObjectShow(newDefaultInputDeviceID);

            if (IsMicDevice(newDefaultInputDeviceID))
                printf("Microphone is being used.\n");
            else printf("No microphone is being used.\n");
        }
    }

    return noErr;
}

int main(int argc, char** argv) {
    AudioDeviceID defaultInputDeviceID;
    UInt32 size = sizeof(AudioDeviceID);
    AudioObjectPropertyAddress propAddress = { kAudioHardwarePropertyDefaultInputDevice,
        kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster };

    OSStatus err = AudioObjectGetPropertyData(kAudioObjectSystemObject, &propAddress,
            0, NULL, &size, &defaultInputDeviceID);
    if (err)
    {
        printf("Get default input device failed.\n");
        return EXIT_FAILURE;
    }

    CFRunLoopRef theRunLoop = CFRunLoopGetCurrent();
    propAddress.mSelector = kAudioHardwarePropertyRunLoop;
    err = AudioObjectSetPropertyData(kAudioObjectSystemObject, &propAddress,
            0, NULL, sizeof(CFRunLoopRef), &theRunLoop);
    if (err)
    {
        printf("Set kAudioHardwarePropertyRunLoop failed with error %ld\n", err);
        return EXIT_FAILURE;
    }

    propAddress.mSelector = kAudioHardwarePropertyDefaultInputDevice;
    err = AudioObjectAddPropertyListener(kAudioObjectSystemObject, &propAddress,
            MyAudioObjectPropertyListenerProc, NULL);
    if (err)
    {
        printf("AudioObjectAddPropertyListener failed with error %ld\n", err);
        return EXIT_FAILURE;
    }

    // Check if current default input device is a mic
    if (IsMicDevice(defaultInputDeviceID))
    {
        printf("Currently a microphone is being used.\n");
    }
    else
    {
        printf("Currently no microphone is being used.\n");
    }

    CFRunLoopRun();

    CFRelease(theRunLoop);

    return (EXIT_SUCCESS);
}
